#include <stdio.h>
#include <windows.h>
#include <mmsystem.h>

#define BUFFERS 2

HWAVEOUT hWaveOut=NULL;

struct BUFFER{
	WAVEHDR  tWaveHeader;
	char     *pBuffer;  
	unsigned long   ulSize;
	unsigned long   ulWritePos;
};

BUFFER tBuffer[BUFFERS];
int bPlaying=0;
int bPaused=0;
int iCurrentWrite=0;
int iSampleRate;
int nBPS;

DWORD WINAPI waveOutProc(HWAVEOUT htWaveOut, UINT uMsg, DWORD dwInst, WPARAM wParam, LPARAM lParam) {
	WAVEHDR *tTempWaveHeader=NULL;

	if(uMsg==WOM_DONE){ //we are done with this buffer, flag it so we can use it again
		tTempWaveHeader=(WAVEHDR*)wParam;
		tTempWaveHeader->dwUser=false;
	}

	return 0;
} 

void audioShutdown() { //shutdown audio engine
	if(hWaveOut){
		waveOutReset(hWaveOut);
		
		for(int iCurBuf=0; iCurBuf<BUFFERS; iCurBuf++) { //delete buffers
			waveOutUnprepareHeader(hWaveOut,&tBuffer[iCurBuf].tWaveHeader,sizeof(WAVEHDR));
			if(tBuffer[iCurBuf].pBuffer) delete tBuffer[iCurBuf].pBuffer;
			memset(&tBuffer[iCurBuf],0,sizeof(BUFFER));
		}

		waveOutClose(hWaveOut);
	}
	
	hWaveOut=NULL;
	bPlaying=0;
	iCurrentWrite=0;
}

//start up the sound engine
unsigned long audioStartup(HWND hWnd, int iSetRate, int iSetDepth)
{
	audioShutdown();

	//set audio format
	WAVEFORMATEX wfx;
	wfx.nSamplesPerSec  = iSetRate;
	wfx.wBitsPerSample  = iSetDepth;
	wfx.nChannels       = 1;
	wfx.cbSize          = 0;
	wfx.wFormatTag      = WAVE_FORMAT_PCM;
	wfx.nBlockAlign     = (wfx.wBitsPerSample / 8) * wfx.nChannels;
	wfx.nAvgBytesPerSec = wfx.nBlockAlign * wfx.nSamplesPerSec;
	
	//open the wave mapper
	if(waveOutOpen(&hWaveOut,WAVE_MAPPER,&wfx,(DWORD)waveOutProc,0,CALLBACK_FUNCTION))
		return 0;

	//initialize buffers
	for(int iCurBuf=0; iCurBuf<BUFFERS; iCurBuf++) { 
		memset(&tBuffer[iCurBuf],0,sizeof(BUFFER));
		tBuffer[iCurBuf].pBuffer=new char[wfx.nAvgBytesPerSec];
		tBuffer[iCurBuf].ulSize=wfx.nAvgBytesPerSec;
		tBuffer[iCurBuf].tWaveHeader.lpData=tBuffer[iCurBuf].pBuffer;
	}

	//reset variables
	bPlaying=bPaused=false;
	iCurrentWrite=0;
	iSampleRate=iSetRate;
	nBPS=iSetDepth;

	return 1;
}

//write to the audio buffer
void audioWrite(char *pBuffer, unsigned long ulSize) {
  if(hWaveOut==NULL) return; //audio engine not started
  
	//unprepare the header if we need to
	if(tBuffer[iCurrentWrite].ulWritePos==0 && tBuffer[iCurrentWrite].tWaveHeader.dwFlags==WHDR_DONE) 
		waveOutUnprepareHeader(hWaveOut,&tBuffer[iCurrentWrite].tWaveHeader,sizeof(WAVEHDR));
	
	//copy the input buffer into the play buffer
	CopyMemory(tBuffer[iCurrentWrite].tWaveHeader.lpData+tBuffer[iCurrentWrite].ulWritePos,pBuffer,ulSize);
	
	tBuffer[iCurrentWrite].ulWritePos+=ulSize; //move the write position
	
	if(bPlaying==0) { //fill some buffers if we havent started playing yet
		//if the buffer it full, prepare it, after 2 full buffers start playing
		if((tBuffer[iCurrentWrite].ulWritePos >= tBuffer[iCurrentWrite].ulSize) || (tBuffer[iCurrentWrite].ulWritePos + ulSize > tBuffer[iCurrentWrite].ulSize)){
			tBuffer[iCurrentWrite].tWaveHeader.dwBufferLength=tBuffer[iCurrentWrite].ulWritePos;
			tBuffer[iCurrentWrite].tWaveHeader.dwUser=true;
			waveOutPrepareHeader(hWaveOut, &tBuffer[iCurrentWrite].tWaveHeader, sizeof(WAVEHDR));
			tBuffer[iCurrentWrite].ulWritePos=0;
			iCurrentWrite=(iCurrentWrite+1)%BUFFERS;
		}
	
		if(iCurrentWrite==BUFFERS>>1) {
			for(int iCurBuf=0; iCurBuf<BUFFERS>>1; iCurBuf++)
				waveOutWrite(hWaveOut,&tBuffer[iCurBuf].tWaveHeader,sizeof(WAVEHDR));
			
			bPlaying=1;
		}
	}
	
	else { //queue up the buffer
		//play buffer if full
		if((tBuffer[iCurrentWrite].ulWritePos>=tBuffer[iCurrentWrite].ulSize) || ((tBuffer[iCurrentWrite].ulWritePos+ulSize)>tBuffer[iCurrentWrite].ulSize)) {
			tBuffer[iCurrentWrite].tWaveHeader.dwBufferLength=tBuffer[iCurrentWrite].ulWritePos;
			tBuffer[iCurrentWrite].tWaveHeader.dwUser=true;
			waveOutPrepareHeader(hWaveOut, &tBuffer[iCurrentWrite].tWaveHeader,sizeof(WAVEHDR));
			waveOutWrite(hWaveOut, &tBuffer[iCurrentWrite].tWaveHeader,sizeof(WAVEHDR));
			tBuffer[iCurrentWrite].ulWritePos=0;
			iCurrentWrite=(iCurrentWrite+1)%BUFFERS;
		}
	}
}

int audioPlaying() {
  if(hWaveOut==NULL) return 0;
  if(bPaused) return 0; 
  if(bPlaying) return 1;
  return 0;
}

void audioPlay() {
	if(hWaveOut) {
		bPaused=0; bPlaying=1;
		waveOutRestart(hWaveOut);
	}
}

void audioPause() {
	if(hWaveOut) {
		bPaused=1; bPlaying=0;
		waveOutPause(hWaveOut);
	}
}

void audioPlayPause() {
	if(audioPlaying()) audioPause();
	else audioPlay();
}

//change the volume of the sound
void audioSetVolume(unsigned long ulSetVolume) {
	if(hWaveOut==NULL) return; //audio engine not started

	unsigned long ulVolume;
	unsigned short  usShort;
	
	//compute the volume
	usShort=(0xFFFF*(unsigned short)ulSetVolume)/100;
	ulVolume=(usShort<<16) | usShort;
	
	waveOutSetVolume(hWaveOut,ulVolume); //set volume
}

int audioCanWrite(unsigned long ulSize) { //check if there is room in the buffer
	if(hWaveOut==NULL) return 0; //audio engine not started

	//can't write if the current write buffer has not played yet
	if(tBuffer[iCurrentWrite].tWaveHeader.dwUser) return 0;
	
	//see if there is room
	if((tBuffer[iCurrentWrite].ulSize-tBuffer[iCurrentWrite].ulWritePos)>=ulSize) return 1;
	
	return 0;
}
